package edu.siu.math.egut.local;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;


import edu.siu.math.egut.io.Algorithms;
import edu.siu.math.egut.local.Inequality.Symbol;
import edu.siu.math.egut.main.OutputStyle;
import edu.siu.math.egut.util.BadSubstitutionException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.PolynomialReadException;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;



/**
 * @author josephhundley
 *
 */
public class Domain implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 6305393747968782079L;
    public static final Pattern  LIE_MATRIX= Pattern.compile("\\s*\\[(\\s*\\[(\\s*\\d\\s*,\\s*)+\\d\\s*\\]\\s*,?\\s*)+\\]\\s*");
    public static final Pattern LIE_MATRIX_PLUS_CONSTRAINTS= Pattern.compile("\\[(\\[(\\d,)*\\d\\]\\s*)\\]WITHCONSTRAINTS.*");
    public static final Pattern STANDARD_FORM= Pattern.compile("(.*,)*\\|(.*,)*");
    private static final int VARIABLES_LINEBREAK_WIDTH = 50;
    private static final int SCREEN_WIDTH = edu.siu.math.egut.main.SectionIntegralCalculator.SCREEN_WIDTH;

    
    private ArrayList<String> variables;
    private ArrayList<Inequality> bounds;
    public static Domain read(String string) throws InequalityReadException, PolynomialReadException, DomainReadException {
	if(LIE_MATRIX.matcher(string).matches())
	    return fullGroup(Algorithms.readLiEMatrix(string));
	if(LIE_MATRIX_PLUS_CONSTRAINTS.matcher(string).matches())
	    return lieMatrixPlusConstraints(string);
	if(STANDARD_FORM.matcher(string).matches())
	    return standardForm(string);

	throw new DomainReadException();
    }
    private static Domain standardForm(String string) throws DomainReadException, InequalityReadException, PolynomialReadException {
	String[] pair = string.split("\\|");
	if(pair.length != 2)
	    throw new DomainReadException();
	String[] variableStrings = pair[0].split(",");
	
	ArrayList<String> variables = new ArrayList<String>();
	for( String s: variableStrings)
	    variables.add(s);
	
	String[] boundStrings = pair[1].split(",");
	ArrayList<Inequality> bounds = new ArrayList<Inequality>();

	for( String s: boundStrings)
	    bounds.add(Inequality.read(s));
	    
	return new Domain(variables, bounds);
    }
    private static Domain lieMatrixPlusConstraints(String string) throws InequalityReadException, PolynomialReadException {
	String[] pair = string.split("WITHCONSTRAINTS");
	int[][] m = Algorithms.readLiEMatrix(pair[0]);
	ArrayList<String> variables = new ArrayList<String>();
	for( int[] a:m)
	    variables.add(Unipotent.defaultString("u",a));
	String[] boundStrings = pair[1].split(",");
	ArrayList<Inequality> bounds = new ArrayList<Inequality>();
	for( String boundString: boundStrings)
	    bounds.add(Inequality.read(boundString));
	return new Domain(variables, bounds);
	
	
    }
    private static Domain fullGroup(int[][] m) {
	ArrayList<String> variables = new ArrayList<String>();
	for( int[] a:m)
	    variables.add(Unipotent.defaultString("u",a));
	return new Domain(variables, new ArrayList<Inequality>());
    }
   /**
 * @param v
 * @param b
 */
public Domain(ArrayList<String> v, ArrayList<Inequality> b){
       variables = v;
       bounds = b;
   }
public Domain(Domain d) {
    variables = new ArrayList<String>();
    variables.addAll(d.variables);
    bounds = new ArrayList<Inequality>();
    bounds.addAll(d.bounds);
}

/**
 * Instantiates a new Domain based on a collection of Strings.  The 
 * Strings will be the variables in the new Domain.  There will be 
 * no bounds.
 * @param allVariables containing the variables for the desired domain
 */
public Domain(final Collection<String> allVariables) {
    variables = new ArrayList<String>(allVariables);
    bounds = new ArrayList<Inequality>();
}
public  Domain update() {
    
    ArrayList<Inequality> newBounds = new ArrayList<Inequality>();
    for( Inequality b: bounds){
	newBounds.add(b.update());
    }
    return new Domain(this.variables, newBounds);

}
public String toString(OutputStyle sty) {
    StringBuilder sb = new StringBuilder();
    switch(sty)
    {
    case TEX:
	sb.append("\\left\\{\\begin{array}{c");
	if(bounds.size() !=0)
	sb.append("|c");
	sb.append("}\n" +
			"\\begin{array}{r}(");
	int widthEstimate = 0;
	int numberOfRows = 1;
	for( int i = 0 ; i < variables.size(); i++){
	    widthEstimate += variables.get(i).length()+2;
	    /* It may be desirable to improve this at some point.
	     * (Note that u_{1232} will display in text as 5 characters
	     * of which four are small.  Thus, the _,{ and } make it shorter
	     * but make our estimate longer.)
	     */
		sb.append(variables.get(i));
		if (i == variables.size() - 1)
		    sb.append(")");
		else {
		    sb.append(", ");
		    if (widthEstimate > VARIABLES_LINEBREAK_WIDTH) {
			sb.append("\\\\ \n");
			numberOfRows++;
			widthEstimate = 5;// in most cases this will cause rows
					  // after the
			// first to be slightly indented.
		    }
		}
	}
	
	sb.append("\\end{array}");
	    sb.append("\\begin{array}{l}");
	
	for( int i = 1 ; i < numberOfRows; i++)
	    sb.append("\\\\ ");
	sb.append("\\in F^{"+variables.size()+"}");
	sb.append("\\end{array}\n");
	if(bounds.size() !=0){
	    sb.append("&\n");
	sb.append("\\begin{array}{c}\n");
	for( int i = 0 ; i < bounds.size(); i++)
	    sb.append(bounds.get(i).toString(OutputStyle.TEX)
		    +"\\\\ \n");
	sb.append("\\end{array}");
	}
	sb.append("\\end{array}\\right\\}");
	break;
    case SCREEN:
	sb.append("variables: \n");
	for(int i= 0; i < variables.size(); i++)
	    sb.append(variables.get(i)+((i%5==4)?"\t":"\n"));
	sb.append("\nconstraints:");
	for(int i= 0; i < bounds.size(); i++)
	    sb.append(bounds.get(i).toString(OutputStyle.SCREEN)+"\n");
	break;
	
    }
    return sb.toString();
    
    
}
public Domain addBound(Polynomial lhs, Symbol sym, Polynomial rhs) {
    ArrayList<Inequality> newBounds = this.bounds;
    newBounds.add(new Inequality(lhs, sym, rhs)); 
    return new Domain(this.variables, newBounds);
}
public ArrayList<String> getVariables() {    
    return variables;
}
public ArrayList<Inequality> getBounds() {
    return bounds;
}
public boolean doesNotConstrain(String variable){
    for( Inequality b: bounds){
	if( b.getLHS().dependsOn(variable) || b.getRHS().dependsOn(variable))
	    return false;
    }
    return true;
}
/**
 * Replace given string with given polynomial throughout.
 * @param s1 String to be replaced 
 * @param p1 Polynomial to replace it with.
 * @throws BadSubstitutionException if the substitution would take some Polynomial 
 * outside the set of Laurent Polynomials
 */
public void replace(String s1, Polynomial p1) throws BadSubstitutionException {
    variables.remove(s1);
    addAll(p1.getAllVariables());
    for(Inequality i: bounds)
	i.replace(s1, p1);
}
private void addAll(List<String> allVariables) {
    for(String x: allVariables)
	if( !variables.contains(x))
	    variables.add(x);
    
}
public boolean hasVariable(String input) {
     return variables.contains(input);
}
public void replace(String var, String string) {
    variables.remove(var);
    if(!variables.contains(string))
	variables.add(string);
    for(Inequality i: bounds)
	i.replace(var, string);
    
}
public void addVariables(ArrayList<Sunipotent> newSunipotents) {
    for(Sunipotent s: newSunipotents){
	addAll(s.getArgument().getAllVariables());
    }
    
}
public Domain removeBound(Polynomial lhs, Symbol symbol,
	Polynomial rhs) {
    Inequality i = new Inequality(lhs, symbol, rhs);
    int j = 0;
    while( j < bounds.size() ){
	if( bounds.get(j).matches(i))
	    bounds.remove(j);
	else
	    j++;
    }
    return this;
}
public void addBound(Inequality newBound) {
    if(!bounds.contains(newBound))
	bounds.add(newBound);
    
}
public String constraintsToString(OutputStyle sty) {
    StringBuilder sb = new StringBuilder();
    int length = (int) (13+Math.floor(Math.log10(Math.max(bounds.size(), 1))));
	sb.append("\n"+variables.size()+" variables, "+ bounds.size() + " constraints: ");
	for(int i= 0; i < bounds.size(); i++){
	    if( length >0 && 
		    length+bounds.get(i).toString(OutputStyle.SCREEN).length() > SCREEN_WIDTH){
		sb.append("\n");
		length =0;
	    }
	    sb.append(bounds.get(i).toString(OutputStyle.SCREEN)+"   ");
	    length = length + bounds.get(i).toString(OutputStyle.SCREEN).length()+3;
	}
    return sb.toString();
}
public void removeBound(int i, Symbol sym, Polynomial var) {
    removeBound(Polynomial.create(i), sym, var);
    
}
public void removeBound(int i, Symbol sym, String var) {
    removeBound(Polynomial.create(i), sym, Polynomial.create(var));
    
}
public boolean hasBound(Polynomial p, Symbol sym, Polynomial q) {
    for( Inequality i: bounds)
	if( i.getLHS() == p
	    &&i.getSymbol() == sym
	    &&i.getRHS() ==  q)
	    return true;
    return false;
}

public boolean hasBound(Polynomial p, Symbol sym, String s) {
    return hasBound( p, sym, Polynomial.create(s));
}

public boolean hasBound(int i, Symbol sym, String s) {
    return hasBound(Polynomial.create(i),sym,s);
}

public boolean hasBound(int i, Symbol sym, Polynomial s) {
    return hasBound(Polynomial.create(i),sym,s);
}

public boolean hasNoNonsimpleBoundsOn(String x) {
	for (Inequality i : bounds)
	    if (i.involves(x)
		    && !i.matches(1, Symbol.GREATERTHANOREQUALTO, x)
		    && !i.matches(-1, Symbol.GREATERTHANOREQUALTO, x)
		    && !i.matches(1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1))
		    && !i.matches(-1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1)))
		return false;
	return true;	    
    
}
public void removeVariable(String x) {
    variables.remove(x);
    
}

    /**
     * @return a Set containing every string that appears as a variable anywhere in 
     * the definition of the domain.  (Either among the listed variables or in the 
     * Polynomial bounds.)
     */
    public Set<String> getAllVariables() {
	final Set<String> allVars = new TreeSet<String>();
	allVars.addAll(variables);
	for (final Inequality i : bounds) {
	    allVars.addAll(i.getLHS().getAllVariables());
	    allVars.addAll(i.getRHS().getAllVariables());
	}
	return allVars;
    }

public boolean equalsVerbose( Domain d){
    
    if(this.variables.size()==d.variables.size()
	    && this.variables.containsAll(d.variables)){
	if(this.bounds.size() == d.bounds.size()
		&& this.bounds.containsAll(d.bounds))
	    return true;
	else{
	    System.out.println("different bounds");
	    return false;
	}}
	else{
	    System.out.println("different variable sets: " +d.variables.size() +" variables in one and "
		    +variables.size() + " in the other. ");
	    TreeSet<String> symmetricDifference = new TreeSet<String>();
	    TreeSet<String> ldiff = new TreeSet<String>();
	    symmetricDifference.addAll(variables);
	    ldiff.addAll(d.variables);
	    symmetricDifference.removeAll(d.variables);
	    ldiff.removeAll(variables);
	    symmetricDifference.addAll(ldiff);
	    for( String s:symmetricDifference)
		System.out.println(s);
	    
	    return false;
	}
	
	
    
}



}
